package com.hdu.degrete.aspect;



import com.hdu.degrete.degrate.BeanAndMethodHolder;
import com.hdu.degrete.degrate.CircuitBreaker;
import com.hdu.degrete.degrate.Degrate;
import com.hdu.degrete.degrate.DegrateException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@Aspect
public class DegrateAspect {


    private final Map<String, CircuitBreaker> CIRCUIT_BREAKER_MAP = new ConcurrentHashMap<>();
    private final Map<String, BeanAndMethodHolder> BEAN_AND_METHOD_HOLDER_MAP = new ConcurrentHashMap<>();


    @Pointcut("@annotation(com.hdu.degrete.degrate.Degrate)")
    public void degrateMethodPt() {

    }


    @Around("degrateMethodPt()")
    public Object handleDegrateMethod(ProceedingJoinPoint pjp) {
        Degrate degrate;
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        degrate = method.getAnnotation(Degrate.class);
        String identity = getIdentity(pjp);
        CIRCUIT_BREAKER_MAP.putIfAbsent(
                identity,
                new CircuitBreaker(
                        degrate.degrateTime(),
                        degrate.timeUnit()
                )
        );
        CircuitBreaker circuitBreaker = CIRCUIT_BREAKER_MAP.get(identity);
        if (!circuitBreaker.tryPass(pjp)) {
            CircuitBreaker.DegrateType degrateType = degrate.degrateType();
            switch (degrateType) {
                case CUSTOM_RESULT:
                    BEAN_AND_METHOD_HOLDER_MAP.putIfAbsent(
                            identity,
                            createBeanAndMethodHolder(degrate)
                    );
                    return BEAN_AND_METHOD_HOLDER_MAP.get(identity).invoke();
                case THROW_EXCEPTION:
                    throw new DegrateException(identity + "is degrate");
                default:
                    throw new UnsupportedOperationException("unsupported degrate type");
            }
        }
        try {
            Object result = pjp.proceed();
            circuitBreaker.success();
            return result;
        } catch (Throwable e) {
            circuitBreaker.fail();
            circuitBreaker.tryFromCloseToOpen(degrate.failRate());
            throw new RuntimeException(e);
        }
    }


    private BeanAndMethodHolder createBeanAndMethodHolder(Degrate degrate) {
        try {
            Object customerResultHandler = degrate.customResultHandler().newInstance();
            return new BeanAndMethodHolder(
                    customerResultHandler,
                    customerResultHandler.getClass().getDeclaredMethod(
                            degrate.methodName()
                    )
            );
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private String getIdentity(ProceedingJoinPoint proceedingJoinPoint) {
        return proceedingJoinPoint.getSignature().toString();
    }
}
